#include "mkvmuxfilter.hpp"
#include <uuids.h>
#include <amvideo.h>
#include <dvdmedia.h>
#include "mkvstreamvideompeg2.hpp"
#include "mkvstreamvideovpx.hpp"
#include "fourccguid.hpp"
//#include "odbgstream.hpp"
//using std::endl;
//using std::boolalpha;

namespace MkvMux
{

//TODO: find a proper home for this
extern const GUID MEDIASUBTYPE_VP80_VIDEO = MJH::FourCCGUID("VP80");

InpinVideo::InpinVideo(Filter* p) :
    Inpin(p, L"video")
{
    AM_MEDIA_TYPE mt;
    
    mt.majortype = MEDIATYPE_Video;
    //mt.subtype = (see below)
    
    mt.bFixedSizeSamples = FALSE;
    mt.bTemporalCompression = TRUE;
    mt.lSampleSize = 0;
    mt.formattype = GUID_NULL;
    mt.pUnk = 0;
    mt.cbFormat = 0;
    mt.pbFormat = 0;
    
    mt.subtype = MEDIASUBTYPE_MPEG2_VIDEO;
    m_preferred.Add(mt);
    
    mt.subtype = MEDIASUBTYPE_VP80_VIDEO;
    m_preferred.Add(mt);
}


InpinVideo::~InpinVideo()
{
}


HRESULT InpinVideo::QueryAccept(const AM_MEDIA_TYPE* pmt)
{
    if (pmt == 0)
        return E_INVALIDARG;
        
    const AM_MEDIA_TYPE& mt = *pmt;
    
    if (mt.majortype != MEDIATYPE_Video)
        return S_FALSE;
    
    if (mt.subtype == MEDIASUBTYPE_MPEG2_VIDEO)
        return QueryAcceptMPEG2(mt);
        
    if (mt.subtype == MEDIASUBTYPE_VP80_VIDEO)
        return QueryAcceptVP80(mt);
        
    return S_FALSE;
}
        


HRESULT InpinVideo::QueryAcceptMPEG2(const AM_MEDIA_TYPE& mt) const
{
    if (mt.pbFormat == 0)
        return S_FALSE;
        
    if (mt.formattype != FORMAT_MPEG2_VIDEO)
        return S_FALSE;

    //if (mt.cbFormat < sizeof(MPEG2VIDEOINFO))
    //    return S_FALSE;
        
    if (mt.cbFormat < (ULONG)FIELD_OFFSET(MPEG2VIDEOINFO, dwSequenceHeader[0]))
        return S_FALSE;

    const MPEG2VIDEOINFO& mpg = (MPEG2VIDEOINFO&)(*mt.pbFormat);

    if (mt.cbFormat < SIZE_MPEG2VIDEOINFO(&mpg))
        return S_FALSE;

    const DWORD cb = mpg.cbSequenceHeader;
    
    if (cb < 4)
        return S_FALSE;

    //http://dvd.sourceforge.net/dvdinfo/mpeghdrs.html#seq

    const BYTE* const h = MPEG2_SEQUENCE_INFO(&mpg);

    if (h[0] != 0)
        return S_FALSE;
        
    if (h[1] != 0)
        return S_FALSE;
        
    if (h[2] != 1)
        return S_FALSE;
        
    if (h[3] != 0xB3)
        return S_FALSE;

    //TODO: parse sequence header
    //width and height must(?) match BMIH

    const VIDEOINFOHEADER2& vih = mpg.hdr;
    const BITMAPINFOHEADER& bmih = vih.bmiHeader;
    bmih;

    return VetBitmapInfoHeader(vih.bmiHeader);
}


HRESULT InpinVideo::QueryAcceptVP80(const AM_MEDIA_TYPE& mt) const
{
    if (mt.pbFormat == 0)
        return S_FALSE;
        
    if (mt.formattype == FORMAT_VideoInfo)
    {
        if (mt.cbFormat < sizeof(VIDEOINFOHEADER))
            return S_FALSE;
            
        const VIDEOINFOHEADER& vih = (VIDEOINFOHEADER&)(*mt.pbFormat);        

        return VetBitmapInfoHeader(vih.bmiHeader);        
    }

    if (mt.formattype == FORMAT_VideoInfo2)
    {
        if (mt.cbFormat < sizeof(VIDEOINFOHEADER2))
            return S_FALSE;

        const VIDEOINFOHEADER2& vih = (VIDEOINFOHEADER2&)(*mt.pbFormat);

        return VetBitmapInfoHeader(vih.bmiHeader);
    }
    
    return S_FALSE;
}


HRESULT InpinVideo::VetBitmapInfoHeader(const BITMAPINFOHEADER& bmih) const
{
    if (bmih.biSize < sizeof(BITMAPINFOHEADER))
        return S_FALSE;
        
    //TODO: we should verify that the sum of the offset to the BMIH and its size
    //is less than cbFormat.
    
    if (bmih.biWidth <= 0)
        return S_FALSE;
        
    if (bmih.biHeight <= 0)
        return S_FALSE;
        
    return S_OK;
}


HRESULT InpinVideo::GetAllocatorRequirements(ALLOCATOR_PROPERTIES* p)
{
    if (p == 0)
        return E_POINTER;
        
    ALLOCATOR_PROPERTIES& props = *p;
    
    assert(m_connection_mt.Size() == 1);

    const AM_MEDIA_TYPE& mt = m_connection_mt[0];

    if (mt.subtype == MEDIASUBTYPE_MPEG2_VIDEO)
        return GetAllocatorRequirementsMPEG2(props);
        
    if (mt.subtype == MEDIASUBTYPE_VP80_VIDEO)
        return GetAllocatorRequirementsVP80(props);
        
    assert(false);
    return E_FAIL;
}


HRESULT InpinVideo::GetAllocatorRequirementsMPEG2(ALLOCATOR_PROPERTIES& props) const
{
    props.cBuffers = 1;  //we copy payload, so we don't hold onto samples
    props.cbBuffer = 0;  //let upstream pin decide size
    props.cbAlign = 0;
    props.cbPrefix = 0;
    
    return S_OK;
}


HRESULT InpinVideo::GetAllocatorRequirementsVP80(ALLOCATOR_PROPERTIES& props) const
{
    //We hold onto samples, so at least as many samples in the as we expect to exist
    //in a cluster.  Assume pessimistically that the framerate is 30 fps, and that
    //keyframes arrive no slower than once every 3 seconds.  
    //
    //TODO: If these assumptions are incorrect, or the upstream pin does not honor
    //are allocator requirements, then the stream will stall.  We should have a 
    //graceful way of handling these cases.
    props.cBuffers = 3 * 30;

    props.cbBuffer = 0;  //let upstream pin decide size
    props.cbAlign = 0;
    props.cbPrefix = 0;
    
    return S_OK;
}


HRESULT InpinVideo::OnInit()
{  
    assert(m_connection);
    assert(m_pStream == 0);
    assert(m_connection_mt.Size() == 1);

    const AM_MEDIA_TYPE& mt = m_connection_mt[0];
    assert(mt.majortype == MEDIATYPE_Video);
    
    MkvContext& mkv = m_pFilter->m_mkv;
    MkvStreamVideo* pStream;
    
    if (mt.subtype == MEDIASUBTYPE_MPEG2_VIDEO)
        pStream = CreateStreamMPEG2(mkv, mt);
        
    else if (mt.subtype == MEDIASUBTYPE_VP80_VIDEO)
        pStream = CreateStreamVP80(mkv, mt);
        
    else
    {
        assert(false);
        return E_FAIL;
    }
        
    assert(pStream);
    
    mkv.SetVideo(pStream);
    m_pStream = pStream;

    return S_OK;
}


MkvStreamVideo*
InpinVideo::CreateStreamMPEG2(MkvContext& mkv, const AM_MEDIA_TYPE& mt)
{
    return new (std::nothrow) MkvStreamVideoMpeg2(mkv, mt);
}


MkvStreamVideo*
InpinVideo::CreateStreamVP80(MkvContext& mkv, const AM_MEDIA_TYPE& mt)
{
    return new (std::nothrow) MkvStreamVideoVPx(mkv, mt);
}


void InpinVideo::OnFinal()
{
   MkvContext& ctx = m_pFilter->m_mkv;
   ctx.SetVideo(0);
}


HANDLE InpinVideo::GetOtherHandle() const
{
    const InpinAudio& ia = m_pFilter->m_inpin_audio;    
    return ia.m_hSample;
}



} //end namespace MkvMux
